from functools import lru_cache
import json
from typing import TypedDict, List
import os
import logging

LOCAL_INSTANCE_DATA_PATH = os.environ.get(
    "LOCAL_INSTANCE_DATA_PATH", os.path.abspath("./instance")
)
DB_URI = ""
TEMPLATE_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__)), "templates")
logger = logging.getLogger(__name__)
BUILTIN_APPS_CONFIG_FILE = os.path.join(
    os.path.dirname(__file__), "docker_app_configs", "docker_apps.yaml"
)
EXTERNAL_APPS_CONFIG_FILE = os.path.join(LOCAL_INSTANCE_DATA_PATH, "docker_apps.yaml")


def _set_db_uri(new_uri: str):
    """
    设置数据库的uri，注意只在测试中使用
    """
    global DB_URI
    logger.warning(
        f"db uri was CHANGED, " "MAKE SURE THIS IS NOT YOUR PRODUCTION ENVIRONMENT!"
    )
    DB_URI = new_uri


class NginxCertFile(TypedDict):
    pem: str  # 以.pem结尾的证书文件
    key: str  # 以.key结尾的证书文件


class SnapshotConfigs(TypedDict):
    snapshot_repo: str  # 快照仓库


class Config(TypedDict):
    dev_mode: bool

    secret_key: str

    nginx_docker_image: str
    nginx_listen_port: int
    nginx_ssl_used: bool
    # 表示nginx域名或ip的配置
    # 在有nginx_domain_name的情况下
    # 如果nginx_domain_name是空，那么点击“打开编辑器”后，跳转到的nginx所在的ip/域名与原网页一致，只是端口不同。
    nginx_domain_name: str
    nginx_ip: str

    # nginx证书文件名
    nginx_cert_filename: NginxCertFile

    # The path to persist data of containers.
    shared_volume_dir: str

    # The path that shared NFS mounted (Not Implemented Yet)
    shared_nfs_dir: str

    # The configs for Snapshots (Not Implemented Yet)
    snapshot_configs: SnapshotConfigs

    # The range of ports that accessible to users
    # For example : ["3000", "3005-3010"]
    # will make BaCO3 expose isolated-port apps over these ports:
    #   3000, 3005, 3006, 3007, 3008, 3009, 3010.
    # NOTE: this configuration does not support refreshing yet
    accessible_ports: list[str]


# 默认配置值
_default_config: Config = {
    "dev_mode": False,
    "secret_key": "your-secret-key",
    "nginx_docker_image": "registry.cn-wulanchabu.aliyuncs.com/sky-group/nginx:1.24.0",
    "nginx_listen_port": 30606,
    "nginx_ssl_used": False,
    "nginx_domain_name": "",
    "nginx_ip": "",
    "nginx_cert_filename": {"pem": "", "key": ""},
    "shared_volume_dir": "/docker_containers_shared",
    # "external_programs_volume_dir": "/config",
    "shared_nfs_dir": "",
    "snapshot_configs": {"snapshot_repo": ""},
    "accessible_ports": None,
}

_config_data: Config


def check_configs():
    """
    检查配置文件是否合法
    """

    def assert_valid_port(port: int):
        if 0 < port <= 65535:
            return
        else:
            raise ValueError(f"Invalid port: {port}")

    def check_accessible_ports():
        if _config_data["accessible_ports"] is None:
            logger.fatal(
                "You must set accessible_ports in configs.json. If you only want to use subroutes, set it to []"
            )
            raise ValueError("Config item 'accessible_ports' is not set")
        for port in _config_data["accessible_ports"]:
            if "-" in port:
                start, end = map(int, port.split("-"))
                assert_valid_port(start)
                assert_valid_port(end)
                if start > end:
                    raise ValueError(f"Invalid port range: {port}")

    check_accessible_ports()


def read_and_rewrite_if_key_not_match(
    config_file: str, data: Config, default_config: Config
):
    """
    读取并重写配置文件，如果文件中的键与默认值中的键不一致，则重写文件
    """
    should_rewrite = False
    for key, value in default_config.items():
        if key not in data:
            data[key] = value
            should_rewrite = True
    if should_rewrite:
        with open(config_file, "w") as file:
            json.dump(data, file, indent=4)
    return data


def load_config(file_path: str):
    global _config_data
    if os.path.exists(file_path):
        with open(file_path, "r") as file:
            data = json.load(file)
        # 对比data和_default_config中的键是否一致，如果后者有键在data中没有，则需要重新初始化一下data
        # 同时合并这些配置。
        _config_data = read_and_rewrite_if_key_not_match(
            file_path, data, _default_config
        )
    else:
        os.makedirs(os.path.dirname(file_path), exist_ok=True)
        _config_data = _default_config  # 如果文件不存在，使用默认值
        with open(file_path, "w") as file:
            json.dump(_config_data, file, indent=4)  # 初始化配置文件
    check_configs()


def inject_config(configs):
    """
    Inject configs into _config_data, only used for testing.
    """
    global _config_data
    _config_data = configs


def get_config() -> Config:
    return _config_data


@lru_cache
def get_accessible_ports() -> set[int]:
    """
    解析端口范围，返回一个包含所有有效端口的列表。
    支持单个端口和端口范围的解析。
    例如：["3000", "3005-3010"] 将返回 [3000, 3005, 3006, 3007, 3008, 3009, 3010]
    """
    ports = []
    for port_range in _config_data["accessible_ports"]:
        if "-" in port_range:
            start, end = map(int, port_range.split("-"))
            ports.extend(range(start, end + 1))
        else:
            ports.append(int(port_range))
    return set(ports)
